import 'dart:ffi' as ffi;
import 'dart:convert' as conv;
import 'dart:io';

import 'package:ffi/ffi.dart' as ffi_pkg;
import 'package:flutter/material.dart';
import 'package:napi/src/third_party/global_env_extensions.dart';
import 'package:napi/src/napi_bridge_lib.dart';

void main() {
  runApp(MyApp(_examples));
}

class Example {
  String title;
  dynamic Function() callback;
  bool runInitially;
  Example(this.title, this.callback, {this.runInitially = true});
}

String _envStatus() {
  try {
    final env = GlobalNapiEnvFFI.load().getGlobalEnv();
    final addr = env.address;
    if (addr == 0) {
      return 'napi_env is NULL (please ensure ETS called native.setEnvFromEts)';
    }
    return 'napi_env ptr: 0x${addr.toRadixString(16)}';
  } catch (e) {
    return 'Failed to get env: $e';
  }
}

String _createStringAndReference() {
  final lib = GlobalNapiEnvFFI.load();
  final env = lib.getGlobalEnv();
  if (env.address == 0) {
    return 'env is NULL, cannot create N-API values';
  }
  final arena = ffi_pkg.Arena();
  try {
    final dartText = 'Hello from HarmonyOS N-API';
    final cStr = dartText.toNativeUtf8(allocator: arena).cast<ffi.Char>();
    final outValue = arena<ffi.Pointer<ffi.Void>>();
    final rcCreate = lib.createStringUtf8(cStr, dartText.length, outValue);
    if (rcCreate != 0) {
      return 'napi_create_string_utf8 failed: $rcCreate';
    }
    final value = outValue.value;
    final outRef = arena<ffi.Pointer<ffi.Void>>();
    final rcRef = lib.createReference(value, 1, outRef);
    if (rcRef != 0) {
      return 'napi_create_reference failed: $rcRef';
    }
    final ref = outRef.value;
    final rcDel = lib.deleteReference(ref);
    if (rcDel != 0) {
      return 'napi_delete_reference failed: $rcDel';
    }
    return 'Created string and ref OK (value=0x${value.address.toRadixString(16)})';
  } catch (e) {
    return 'Error: $e';
  } finally {
    arena.releaseAll();
  }
}

String _platformInfo() => 'Platform: ${Platform.operatingSystem}';

final _examples = <Example>[
  Example('Env status', _envStatus),
  Example('Create string + reference via bridge', _createStringAndReference,
      runInitially: false),
  Example('Platform info', _platformInfo, runInitially: false),
  Example('String.valueOf(1332) [TSFN]', () => _stringValueOfTsfn(1332), runInitially: false),
  Example('Generate random number (0..180) [TSFN]', () => _randomUsingJsTsfn(180), runInitially: false),
  Example('Math.random() [TSFN]', () => _mathRandomTsfn(), runInitially: false),
];

class MyApp extends StatefulWidget {
  const MyApp(this.examples, {super.key});
  final List<Example> examples;

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('N-API Examples (HarmonyOS)')),
        body: ListView.builder(
          itemCount: widget.examples.length,
          itemBuilder: (context, i) => ExampleCard(widget.examples[i]),
        ),
      ),
    );
  }
}

String _stringValueOf(int n) {
  try {
    final lib = GlobalNapiEnvFFI.load();
    final env = lib.getGlobalEnv();
    if (env.address == 0) throw StateError('env NULL');
    final arena = ffi_pkg.Arena();
    try {
      final outGlobal = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getGlobal(outGlobal) != 0) throw StateError('getGlobal failed');
      final global = outGlobal.value;
      final outStringCtor = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getNamedProperty(global, 'String'.toNativeUtf8(allocator: arena).cast<ffi.Char>(), outStringCtor) != 0) {
        throw StateError('getNamedProperty(String) failed');
      }
      final argc = 1;
      final argv = arena<ffi.Pointer<ffi.Void>>(argc);
      final outInt = arena<ffi.Pointer<ffi.Void>>();
      if (lib.createInt32(n, outInt) != 0) throw StateError('createInt32 failed');
      argv[0] = outInt.value;
      final outValue = arena<ffi.Pointer<ffi.Void>>();
      // 注意：napi_call_function 需要在 JS 线程调用，若非 JS 线程会抛错
      if (lib.callFunction(global, outStringCtor.value, argc, argv, outValue) != 0) {
        throw StateError('String() call failed (not on JS thread?)');
      }
      final sizePtr = arena<ffi.Size>();
      lib.getValueStringUtf8(outValue.value, ffi.Pointer<ffi.Char>.fromAddress(0), 0, sizePtr);
      final len = sizePtr.value;
      final buf = arena<ffi.Char>(len + 1);
      lib.getValueStringUtf8(outValue.value, buf, len + 1, sizePtr);
      final u8 = buf.cast<ffi.Uint8>();
      final bytes = u8.asTypedList(len);
      return conv.utf8.decode(bytes);
    } finally {
      // ignore: avoid_catches_without_on_clauses
      arena.releaseAll();
    }
  } catch (_) {
    // 回退到 Dart 等价实现，避免跨线程崩溃
    return n.toString();
  }
}

String _stringValueOfTsfn(int n) {
  try {
    final env = GlobalNapiEnvFFI.load().getGlobalEnv();
    if (env.address == 0) throw StateError('env NULL');
    final bridge = NapiBridgeLib.load();
    final s = bridge.stringValueOfSync(n);
    if (s == null) throw StateError('tsfn not available');
    return s;
  } catch (_) {
    return n.toString();
  }
}

String _randomUsingJs(int upper) {
  try {
    final lib = GlobalNapiEnvFFI.load();
    final env = lib.getGlobalEnv();
    if (env.address == 0) throw StateError('env NULL');
    final arena = ffi_pkg.Arena();
    try {
      final outGlobal = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getGlobal(outGlobal) != 0) throw StateError('getGlobal failed');
      final global = outGlobal.value;
      final outMath = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getNamedProperty(global, 'Math'.toNativeUtf8(allocator: arena).cast<ffi.Char>(), outMath) != 0) throw StateError('get Math failed');
      final outRandom = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getNamedProperty(outMath.value, 'random'.toNativeUtf8(allocator: arena).cast<ffi.Char>(), outRandom) != 0) throw StateError('get random failed');
      final outVal = arena<ffi.Pointer<ffi.Void>>();
      if (lib.callFunction(outMath.value, outRandom.value, 0, ffi.Pointer.fromAddress(0), outVal) != 0) throw StateError('call random failed');
      final dPtr = arena<ffi.Double>();
      if (lib.getValueDouble(outVal.value, dPtr) != 0) throw StateError('get double failed');
      final rnd = dPtr.value;
      final res = (rnd * upper).floor();
      return res.toString();
    } finally {
      arena.releaseAll();
    }
  } catch (_) {
    // Dart 回退
    final v = (DateTime.now().microsecondsSinceEpoch % 1000000) / 1000000.0;
    return (v * upper).floor().toString();
  }
}

String _randomUsingJsTsfn(int upper) {
  try {
    final env = GlobalNapiEnvFFI.load().getGlobalEnv();
    if (env.address == 0) throw StateError('env NULL');
    final bridge = NapiBridgeLib.load();
    final d = bridge.mathRandomSync();
    if (d == null) throw StateError('tsfn not available');
    final res = ((d * upper).floor());
    return res.toString();
  } catch (_) {
    final v = (DateTime.now().microsecondsSinceEpoch % 1000000) / 1000000.0;
    return (v * upper).floor().toString();
  }
}

String _mathRandom() {
  try {
    final lib = GlobalNapiEnvFFI.load();
    final env = lib.getGlobalEnv();
    if (env.address == 0) throw StateError('env NULL');
    final arena = ffi_pkg.Arena();
    try {
      final outGlobal = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getGlobal(outGlobal) != 0) throw StateError('getGlobal failed');
      final global = outGlobal.value;
      final outMath = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getNamedProperty(global, 'Math'.toNativeUtf8(allocator: arena).cast<ffi.Char>(), outMath) != 0) throw StateError('get Math failed');
      final outRandom = arena<ffi.Pointer<ffi.Void>>();
      if (lib.getNamedProperty(outMath.value, 'random'.toNativeUtf8(allocator: arena).cast<ffi.Char>(), outRandom) != 0) throw StateError('get random failed');
      final outVal = arena<ffi.Pointer<ffi.Void>>();
      if (lib.callFunction(outMath.value, outRandom.value, 0, ffi.Pointer.fromAddress(0), outVal) != 0) throw StateError('call random failed');
      final dPtr = arena<ffi.Double>();
      if (lib.getValueDouble(outVal.value, dPtr) != 0) throw StateError('get double failed');
      return dPtr.value.toString();
    } finally {
      arena.releaseAll();
    }
  } catch (_) {
    // Dart 回退
    final v = (DateTime.now().microsecondsSinceEpoch % 1000000) / 1000000.0;
    return v.toString();
  }
}

String _mathRandomTsfn() {
  try {
    final env = GlobalNapiEnvFFI.load().getGlobalEnv();
    if (env.address == 0) throw StateError('env NULL');
    final bridge = NapiBridgeLib.load();
    final d = bridge.mathRandomSync();
    if (d == null) throw StateError('tsfn not available');
    return d.toString();
  } catch (_) {
    final v = (DateTime.now().microsecondsSinceEpoch % 1000000) / 1000000.0;
    return v.toString();
  }
}

class ExampleCard extends StatefulWidget {
  const ExampleCard(this.example, {super.key});
  final Example example;

  @override
  State<ExampleCard> createState() => _ExampleCardState();
}

class _ExampleCardState extends State<ExampleCard> {
  bool _run = false;

  @override
  void initState() {
    super.initState();
    _run = widget.example.runInitially;
  }

  Widget _pad(Widget w, double h, double v) =>
      Padding(padding: EdgeInsets.symmetric(horizontal: h, vertical: v), child: w);

  @override
  Widget build(BuildContext context) {
    final eg = widget.example;
    var result = '';
    var hasError = false;
    if (_run) {
      try {
        result = eg.callback().toString();
      } catch (e) {
        hasError = true;
        result = e.toString();
      }
    }
    var resultStyle = const TextStyle(fontFamily: 'Monospace');
    if (hasError) {
      resultStyle = const TextStyle(fontFamily: 'Monospace', color: Colors.red);
    }
    return Card(
      child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
        Text(eg.title, softWrap: true,
            style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
        _pad(Text(result, softWrap: true, style: resultStyle), 8, 16),
        _pad(
          ElevatedButton(
            onPressed: () => setState(() => _run = true),
            child: Text(_run ? 'Run again' : 'Run'),
          ),
          8,
          8,
        ),
      ]),
    );
  }
}

